/*
 * FreeSpaceFile.cpp
 *
 */

#include "FreeSpaceFile.h"
#include "../../log/Logger.h"
#include "../../utils/Utils.h"

FreeSpaceFileException::FreeSpaceFileException(const std::string& msg) throw():
msg(msg) {}

FreeSpaceFileException::~FreeSpaceFileException() throw() {}

const char* FreeSpaceFileException::what() const throw() {
	return msg.c_str();
}

FreeSpaceFile::FreeSpaceFile(BigInt blocksPerRead, BigInt blockSize):
 blocks(blocksPerRead*sizeof(BigInt)), blockSize(blockSize), blocksPerRead(blocksPerRead) {
	indexBeg = 0;
	blocksRead = 0;
}

FreeSpaceFile::~FreeSpaceFile() {
}

void FreeSpaceFile::push(BigInt freeSpace) {
	if (freeSpace > blockSize)
		throw FreeSpaceFileException("maximum block size exceed");
	getDescriptor().seekp(0, std::fstream::end);
	getDescriptor().write((char*)&freeSpace, sizeof freeSpace);
	getDescriptor().flush();
	Logger::getInstance().debug("FreeSpaceFile: se agrego el espacio libre en posicion " + Utils::toString(getDescriptor().tellp()/sizeof(BigInt) - 1) + ": " + Utils::toString(freeSpace) + " - pos: " + Utils::toString(getDescriptor().tellp()));
	rewind();
}

BigInt FreeSpaceFile::findSpace(BigInt amount) {
	rewind();
	bool found = false;
	BigInt pos = 0;
	while (!found && readFromFile()) {
		for (pos = 0; pos < blocksRead && !found; pos++) {
			if (blocks[pos] >= amount) {
				found = true;
				break;
			}
		}
	}
	if (!found) {
		getDescriptor().seekg(0, std::fstream::end);
		Logger::getInstance().debug("FreeSpaceFile: no se encontro espacio. Proximo numero de bloque: " + Utils::toString(getDescriptor().tellg()/sizeof(BigInt)));
		return getDescriptor().tellg()/sizeof(BigInt);
	}
	logBuffer();
	return pos + indexBeg;
}

void FreeSpaceFile::setFreeSpace(BigInt block, BigInt amount) {
	if (amount > blockSize)
		throw FreeSpaceFileException("maximum block size exceed" + Utils::toString(amount) + " in " + Utils::toString(blockSize));
	bool found = findBufferContainingBlock(block);
	if (!found) {
		push(amount);
	}
	else {
		BigInt pos = block - indexBeg;
		blocks[pos] = amount;
		writeToFile();
		Logger::getInstance().debug("FreeSpaceFile: espacio libre en posicion " + Utils::toString(block) + ": " + Utils::toString(amount));
	}
}

BigInt FreeSpaceFile::getFreeSpace(BigInt block) {
	BigInt freeSpace = 0;
	bool found = findBufferContainingBlock(block);
	if (!found)
		throw FreeSpaceFileException("index out of bounds");
	BigInt pos = block - indexBeg;
	freeSpace = blocks[pos];
	return freeSpace;
}

bool FreeSpaceFile::findBufferContainingBlock(BigInt block) {
	bool found = false;
	if (block >= indexBeg && block < indexBeg + blocksRead)
		found = true;
	else {
		getDescriptor().seekg(0, std::fstream::end);
		BigInt fileSize = getDescriptor().tellg();
		if (block*sizeof(BigInt) < fileSize) {
			BigInt start = (block/blocks.size())*blocks.size();
			Logger::getInstance().debug("FreeSpaceFile: buscando el bloque " + Utils::toString(block) + " a partir del bloque " + Utils::toString(start));
			getDescriptor().seekg(start*sizeof(BigInt), std::fstream::beg);
			return readFromFile();
		}
	}
	return found;
}

void FreeSpaceFile::rewind() {
	getDescriptor().seekg(0, std::fstream::beg);
	indexBeg = 0;
	blocksRead = 0;
}

bool FreeSpaceFile::readFromFile() {
	BigInt beg = getDescriptor().tellg();
	indexBeg = beg/sizeof(BigInt);
	getDescriptor().read((char*)&blocks[0], blocks.size());
	if (!getDescriptor().good())
		getDescriptor().clear();
	BigInt end = getDescriptor().tellg();
	blocksRead = (end/sizeof(BigInt)) - indexBeg;
	Logger::getInstance().debug("FreeSpaceFile: leido de " + Utils::toString(beg) + " a " + Utils::toString(end));
	if (blocksRead == 0)
		return false;
	logBuffer();
	return true;
}

void FreeSpaceFile::writeToFile() {
	getDescriptor().seekp(indexBeg*sizeof(BigInt), std::fstream::beg);
	getDescriptor().write((char*)&blocks[0], blocksRead*sizeof(BigInt));
	getDescriptor().flush();
}

void FreeSpaceFile::logBuffer() {
	String aux = Utils::toString(indexBeg) + "(" + Utils::toString(blocksRead) + "): ";
	for (BigInt x = 0; x < blocksRead; x++) {
		aux += "[" + Utils::toString(blocks[x]) + "]";
	}
	Logger::getInstance().debug(aux);
}
